#include <vector>
#include <tr1/memory>
#include "TTSlave.h"
#include "TTI.h"
#include "Identity.h"
#include "Packets.h"

using namespace std;

namespace TTP {
   volatile bool signaled;
   
   void SIGUSR1_handl(int i) { signaled = true; }
   void SIGALRM_handl(int i) { 
      /* TODO */
      // Expire group data
      // send IIS every 5 sec
   }
   
   template<class C> void processPacket(C * p);
   
   template<>
   void processPacket<OHAI>(OHAI * p) {
      TTI* tti = TTI::get();
      id_hash sHash =  p->getSourceIdHash();
      
      lockObj lk(&(tti->lock));
      if(tti->mTTI_State == TTI::LOBBY ||tti->mTTI_State == TTI::JOINED) {
         
         if(sHash == tti->mID) {
            tti->outbox.push_back(new NOWAI(tti->mID));
            tti->_signal();
            return;
         } else {
            vector<byte> app;
            IIS* is = new IIS(tti->mID);
            vector<byte> pkey(tti->mKey->getPublicKey());
            is->setPubKey(pkey);
            FOR_EACH(addrGrp, tti->mGroups) {
               FOR_EACH(grp,addrGrp->second) {
                  is->addMulticastGroup(*(grp->second));
               }
            } 
            tti->mIISSend(app);
            is->addAppData(app);
            tti->outbox.push_back(is);
            tti->_signal();
            return;
         }
         
      }
      
      // if p->sid == mID:
      //    outbox << new NOWAI(mID);
      //    raise(SIGUSR1);
      //
      // if joined:
      //    FOR_EACH(parp, curGroup->participant) {
      //       Participant &par = **parp;
      //       if(par.ID == p->sid) return;
      //    }
      //
      // vector<byte> app;
      // vector<Group> groups = getGroups();
      // mIISSend(app);
      // outbox << new IIS(mID, mKey.getPubKey(), groups, app);
      // raise(SIGUSR1);
   }
   
   template<>
   void processPacket<IIS>(IIS * p) {
      TTI* tti = TTI::get();
      id_hash destHash = p->getDestIdHash();
      id_hash sourceHash = p->getDestIdHash();
      
      lockObj lk(&tti->lock);
      if(destHash == 0) { // heartbeat
         /*
          do some sort of update on a cache or something
          */
      } else if(destHash == tti->mID) { // to us, we're probably tring to join a group
         Crypt::Identity* newId;
         if(! (tti->containsIdentity(sourceHash)) ) { //unknown person
            vector<byte> pubKey = p->getPubKey();
            newId = new Crypt::Identity(pubKey);
            tti->_curGroup()->addIdentity(newId);
         } else {
            newId = tti->_curGroup()->getIdentity(sourceHash);
         }
         
         
         for(byte i = 0; i < p->getGroupcount(); i++) {
            if( tti->mGroups.find(p->getMcastAddr(i)) != tti->mGroups.end()) { // unknown group
               Group* newGroup = new Group(p->getMcastAddr(i), p->getGroupKey());
               tti->mGroups[groups[i].getAddress()] = groups[i]; // add info
               assert(groups[i].getAddress() == p->getMcastAddr(i));
            } 
         }
         
         tti->mIISRecv(tti->_curGroup(), newId, p->getAppData());
      }
      // Ignore everything else
      // 
      
      // possibly update group information
      // if(mIISInvoker)
      //    mIISInvoker(p->appData);
   }
   
   template<>
   void processPacket<CANHAS>(CANHAS * p) {
      TTI* tti = TTI::get();
      lockObj lk(&tti->lock);
      uint32_t sourceHash = p->getSourceIdHash();
      
      int lastVote = tti->mVoteMap[tti->mID][sourceHash];
      
      Crypt::Identity* newId = 
      
      if(lastVote == 0) {// no vote
         tti->mHASFLAVRSend(tti->)
      } else if(tti->curGroup()->getLowestID() == tti->mID) {
         bool votesIn = true;
         int yes = 0, no = 0;
         vector<Crypt::Identity*> grpMmbrs =  tti->_curGroup()->getAllIdentities();
         
         FOR_EACH(usr, grpMmbrs) {
            uint32_t userHash = (*usr)->getIdHash();
            
            if(mVoteMap[userHash][sourceHash] == 0) {
               votesIn = false;
            } else if (mVoteMap[userHash][sourceHash] == HASFLAVR::HASFLAVR_no) {
               ++no;
            } else if (mVoteMap[userHash][sourceHash] == HASFLAVR::HASFLAVR_yes) {
               ++yes;
            }
            
         }
         
         if(votesIn) {
            bool accepted = ((yes+no)/2 ) < yes;
            if(accepted) {
               DOWANT dw = new DOWANT();
               dw->setDestIDHash(sourceHash);
               dw->setGroupPrivateKey(tti->_curGroup()->getGroupRSA().getPrivateKey());
               dw->setSymmetricKey(tti->_curGroup()->getGroupBlowfish().getKey());
               tti->mApplicants[sourceHash]->getEKey().encrypt(*dw);
               tti->_curGroup()->getGroupRSA().sign(*dw);
               outbox << (*dw);
            } else {
               LAWL lol = new LAWL();
               lol->setGroupPublicKey(tti->_curGroup()->getGroupRSA().getPublicKey());
               tti->_curGroup()->getGroupRSA().sign(*lol);
               outbox << *lol;
            }
         }
      }
      
      /* We need to store the public key, but where?*/
      
      
      // if vote_map[p->sid][mID] not in vote_map:
      //    resp = Ask client yes/no
      //    vote_map[p->sid][mID] = resp;
      //
      // outbox << new Vote(mID, p->sid, resp)
   }
   
   template<>
   void processPacket<LAWL>(LAWL * p) {
      // If !joined and for us
      //    sadfaic
      //    call LAWLGet
      // If voting
      //    clear Q
   }
   
   template<>
   void processPacket<DOWANT>(DOWANT * p) {
      TTI* tti = TTI::get();
      uint32_t destHash = p->getDestIdHash();
      
      if(destHash == tti->mID && tti->mTTI_State != TTI::JOINED) {
         
         /*Is this already curGroup, do we need to set it some how?*/
      }
   }
   
   template<>
   void processPacket<CHEEZBURGER>(CHEEZBURGER * p) {
      TTI tti = TTI::get();
      uint32_t srcHash =  p->getSourceIdHash();
      
      vector<Crypt::Identity*> identities =  tti->curGroup()->getAllIdentities();
      FOR_EACH(id,identities) {
         uint32_t thisAck = p->getAckNumber(id->getIdHash());
         /*Do something with thisAck*/
         /*if the ACK is really low...send something out or somesuch*/
      }
      
      vector<uint32_t> ackNumbersInData = p->getDataAckNumbers();
      FOR_EACH(ackNum, ackNumbersInData) {
         /*ackNum should get checked and then applied if it's new*/
         /*update ack table*/
      }
      
      
   }
   
   template<>
   void processPacket<HASFLAVR>(HASFLAVR * p) {
      /*add vote to vote map*/
      /*if we don't have all votes, send out packet asking for votes*/
      /*If we have all votes and we're the lowest ID, send out responce*/
      /*If it's a request for votes, send out vote*/
   }
   
   template<class C>
   void verifyAndProcess(C *p) {
      processPacket(p);
   }
   
   template<>
   void verifyAndProcess<LAWL>(LAWL *p) {
      TTI &tti = *TTI::get();
      if(tti.mTTI_State == LOBBY || tti.mTTI_State == JOINED) {
         if(p->getDestIdHash() == tti.mID) {
            shared_ptr<Group> g(tti._curGroup());
            
            if(g) {
               try {
                  g->getGroupRSA().verify(*p);
                  processPacket(p);
               } catch (identity_exception & ex) {
                  cerr << "Verification failed: " << ex.what() << endl;
               } catch (verification_exception & ex) {
                  cerr << "Verification failed: " << ex.what() << endl;
               } catch (encryption_exception & ex) {
                  cerr << "Verification failed: " << ex.what() << endl;
               }
            } else {
               // no valid group... should never get here
               throw msg_exception("JOINED, but no current group.");
            }
         } else {
            // monitor
            // TODO
            assert(0);
         }
      } else {
         // ignore
      }
   }
   
   template<>
   void verifyAndProcess<DOWANT>(DOWANT *p) {
      TTI &tti = *TTI::get();
      if(tti.mTTI_State == LOBBY || tti.mTTI_State == JOINED) {
         if(p->getDestIdHash() == tti.mID) {
            // either joined w/ group or trying to join
            shared_ptr<Group> g(tti._curGroup());
            
            if(g) {
               try {
                  g->getGroupRSA().verify(*p);
                  tti.mKey.decrypt(*p);
                  processPacket(p);
               } catch (identity_exception & ex) {
                  cerr << "Verification failed: " << ex.what() << endl;
               } catch (verification_exception & ex) {
                  cerr << "Verification failed: " << ex.what() << endl;
               } catch (encryption_exception & ex) {
                  cerr << "Verification failed: " << ex.what() << endl;
               }
            } else {
               // no valid group... should never get here
               throw msg_exception("JOINED, but no current group.");
            }
         } else {
            // monitor
            // TODO
            assert(0);
         }
      } else {
         // ignore
      }
   }
   
   template<>
   void verifyAndProcess<HASFLAVR>(HASFLAVR *p) {
      TTI &tti = *TTI::get();
      if(tti.mTTI_State == JOINED) {
         shared_ptr<Group> g(tti._curGroup());
         
         if(g) {
            try {
               g->getIdentity(p->getSourceIdHash())->getSignKey()->verify(*p);
               g->getGroupBlowfish().decrypt(*p);
               processPacket(p);
            } catch (identity_exception & ex) {
               cerr << "Verification failed: " << ex.what() << endl;
            } catch (verification_exception & ex) {
               cerr << "Verification failed: " << ex.what() << endl;
            } catch (encryption_exception & ex) {
               cerr << "Verification failed: " << ex.what() << endl;
            }
         } else {
            // no valid group... should never get here
            throw msg_exception("JOINED, but no current group.");
         }
      } else {
         // ignore
      }
   }
   
   template<>
   void verifyAndProcess<CHEEZBURGER>(CHEEZBURGER *p) {
      TTI &tti = *TTI::get();
      if(tti.mTTI_State == JOINED) {
         shared_ptr<Group> g(tti._curGroup());
         
         if(g) {
            try {
               g->getIdentity(p->getSourceIdHash())->getSignKey()->verify(*p);
               g->getGroupBlowfish().decrypt(*p);
               processPacket(p);
            } catch (identity_exception & ex) {
               cerr << "Verification failed: " << ex.what() << endl;
            } catch (verification_exception & ex) {
               cerr << "Verification failed: " << ex.what() << endl;
            } catch (encryption_exception & ex) {
               cerr << "Verification failed: " << ex.what() << endl;
            }
         } else {
            // no valid group... should never get here
            throw msg_exception("JOINED, but no current group.");
         }
      } else {
         // ignore
      }
   }
   
#define ITEM(name) case Packet::Packet_##name: verifyAndProcess<name>(dynamic_cast<name*>(p)); break
   void processIncomingPackets() {
      TTI &tti = *TTI::get();
      
      {
         lockObj lk(&tti.sock.lock);
         
         // don't want to wait() with lock. (See PIP1)
         // if we disconnect while reading, will get EINTR from
         // sock.select(), which will allow the socket to be disconnected
         // before we get back to here.  Need to have the lock on socket
         // while evaluating guard loop.
         
         while(!tti.signaled() && tti.sock.connected()) {
            try {
               vector<byte> vec;
               tti.sock.select(vec, tti.pipe[0]); // either works or throws EINTR_exception
               // if we got here, will catch signal on next time around loop
               
               lk.suspend(); // unlock socket, avoid deadlock in case of TTI modification
               
               {  // force lk2 initialization after lk.suspend()
                  lockObj lk2(&tti.lock); // need tti.lock to change TTI state
                  lk.resume(); 
                  
                  shared_ptr<Packet> sp(getPacket(vec));
                  
                  if (sp) {
                     switch(sp->type) {
                           ITEM(OHAI);
                           ITEM(IIS);
                           ITEM(CANHAS);
                           ITEM(HASFLAVR);
                           ITEM(LAWL);
                           ITEM(DOWANT);
                           ITEM(CHEEZBURGER);
                        default:
                           cerr << "Unrecognized packet type(" << sp->type << ")" << endl;
                     }
                  } else {
                     throw msg_exception("Null Packet* from buildPacket");
                  }
                  
                  lk.suspend();
               }
            } catch (parsing_exception &ex) {
               cerr << "Error (Parsing): " << ex.what() << endl;
            } catch (EINTR_exception ex) { 
               // caught signal while waiting for a packet.  
               // Need to process events, so return.
               signaled = false;
               return; 
            }
            lk.resume(); // relock socket so loop guard works.
         }
         if(signaled) {
            signaled = false;
            return;
         }
      }
      
      // PIP1: Not connected, wait forever until we're signaled
      wait(NULL);
      assert(errno == EINTR && "Wait canceled from somthing other than EINTR");
   }
#undef ITEM
   
   void sendOutgoingPackets() {
      TTI &tti = *TTI::get();
      lockObj lk(&tti.lock);
      lockObj lk2(&tti.sock.lock);
      
      FOR_EACH(pkt, tti.outbox) {
         vector<byte> PDU;
         pkt.toVec(PDU);
         tti.sock.send(PDU);
         delete *pkt;
      }
      
   }
   
   void * worker(void*) {
      TTI &tti = *TTI::get();
      
      // set up NOP handler for SIGUSR1
      struct sigaction sa;
      sa.sa_mask = 0;
      sa.sa_flags = 0;
      sa.sa_handler = SIGUSR1_handl;
      // note that this sets sigaction for WHOLE PROCESS, not just thread
      sigaction(SIGUSR1, &sa, NULL); 
      
      // TODO: replace with pipe.  have select block on pipe as well, write to it
      // to interrupt select, instead of sigaction.
      
      // add sigaction for SIGALARM with SA_RESTART, so we can do periodic sends
      sa.sa_mask = 0;
      sa.sa_flags = SA_RESTART;
      sa.sa_handler = SIGALRM_handl;
      sigaction(SIGALRM, &sa, NULL); 
      
      for (;;) {
         sendOutgoingPackets();
         processIncomingPackets();
      }
   }
}
